from typing import Iterator, Deque, Callable
from collections import deque

from chernc.treediff.gumtree.tree.tree import TreeLike

class TreeVisitor:
    """
    Interface for visiting a tree in post-order traversal.
    """

    @staticmethod
    def visit_tree(root: TreeLike, visitor: 'TreeVisitor') -> None:
        """
        Start visiting the given tree using the given visitor.
        The tree is visited in post-order.
        """
        stack: Deque[tuple[TreeLike, Iterator[TreeLike]]] = deque()
        stack.append((root, iter(root.get_children())))
        visitor.start_tree(root)
        while stack:
            node, children_iter = stack[-1]
            child = next(children_iter, None)
            if child is None:
                visitor.end_tree(node)
                stack.pop()
            else:
                stack.append((child, iter(child.get_children())))
                visitor.start_tree(child)

    def start_tree(self, tree: TreeLike) -> None:
        """
        Callback executed when entering a node during the visit.
        The visited node is provided as a parameter of the callback.
        """
        raise NotImplementedError

    def end_tree(self, tree: TreeLike) -> None:
        """
        Callback executed when exiting a node during the visit.
        The visited node is provided as a parameter of the callback.
        """
        raise NotImplementedError


class DefaultTreeVisitor(TreeVisitor):
    """
    A default visitor that does nothing for each node.
    """
    def start_tree(self, tree: TreeLike) -> None:
        pass

    def end_tree(self, tree: TreeLike) -> None:
        pass


class InnerNodesAndLeavesVisitor(TreeVisitor):
    """
    A visitor that distinguishes between inner nodes and leaf nodes.
    """
    def start_tree(self, tree: TreeLike) -> None:
        if tree.is_leaf():
            self.visit_leaf(tree)
        else:
            self.start_inner_node(tree)

    def end_tree(self, tree: TreeLike) -> None:
        if not tree.is_leaf():
            self.end_inner_node(tree)

    def start_inner_node(self, tree: TreeLike) -> None:
        """
        Callback executed when entering an inner node during the visit.
        """
        pass

    def visit_leaf(self, tree: TreeLike) -> None:
        """
        Callback executed when visiting a leaf node during the visit.
        """
        pass

    def end_inner_node(self, tree: TreeLike) -> None:
        """
        Callback executed when exiting an inner node during the visit.
        """
        pass
